package tv.geir.slackspace.space;

import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import tv.geir.slackspace.code.ProminentClassLoader;
import tv.geir.slackspace.common.ProminentObject;
import tv.geir.slackspace.common.UuidGenerator;
import tv.geir.slackspace.matching.Template;
import tv.geir.slackspace.server.ServerLease;

/**
 * The space accessor is used to access the object space through simple write, read
 * and take operations. <code>SimplePoolAccessor</code> has no transactional 
 * or locking support.
 * <p>
 * If there are no matching entries both <code>read()</code> and <code>take()</code>
 * block for the specified amount of milliseconds and wait for a matching entry
 * to appear on the space. If no entry appears within the waiting time, <code>null</code> 
 * is returned
 * 
 * @author Geir Gullestad Pettersen
 *
 */
public class SimpleSpaceAccessor implements SpaceAccessor{
	
	/**
	 * Logger 
	 */
	private static Log log = LogFactory.getLog( SimpleSpaceAccessor.class );
	
	
	/**
	 * Classloader for this objects in the space this accesor is accessing.
	 */
	private ProminentClassLoader classLoader; 
	
	/**
	 * Generates unique id's for Leases
	 */
	private UuidGenerator uuidGenerator;
	
	/**
	 * The space to access
	 */
	private ObjectSpace objectSpace;
		
	/**
	 * List of threads that are waiting for a specific object to appear
	 * on the space. Threads should add and remove themselves to/from
	 * this list when the start/stop waiting
	 */
	private Set<Thread> waitingThreads;
	
	/**
	 * Constructor, creates a SpaceAccessor and a default space.
	 *
	 */
	public SimpleSpaceAccessor() {
		waitingThreads = new LinkedHashSet<Thread>();
		uuidGenerator = new UuidGenerator();
		
		classLoader = new ProminentClassLoader(); 
		if ( log.isInfoEnabled() ) {
			log.info( "SimpleSpaceAccessor created" );
		}
	}
	
	
	
	/* (non-Javadoc)
	 * @see tv.geir.slackspace.server.space.ObjectPool#write(tv.geir.slackspace.code.MobileObject, long)
	 */
	public synchronized ServerLease write(ProminentObject mo, long leaseTime) {		
		long timeWrittenToSpace = System.currentTimeMillis();
		ServerLease lease = new ServerLease( leaseTime, timeWrittenToSpace , uuidGenerator.newUuid() );
		mo.setClassLoader( classLoader );
		objectSpace.add( mo, lease );
		
		// notify all waiting threads that a new object has arrived
		notifyAll();
		
		// TODO notify listeners
		return lease;
	}
	
	private void notifyWaitingThreads() {
		for( Thread t : waitingThreads ) {
			t.interrupt();
		}
	}

	/**
	 * Read an object from the pool. If no matching object, method blocks for <code>timeout</code>
	 * seconds and waits for object to arrive. If no matching object arrives whitin
	 * <code>timeout</code> seconds <code>null</code> is returned.
	 * 
	 */
	public ProminentObject read(Template t, long timeout) {		
		if( log.isDebugEnabled() ) {
			log.debug( "Read: " + t );
		}
		ProminentObject o = readOrWait( t, timeout );
		
		if( o == null ) {
			throw new EntryNotFoundException( "Entry does not exist" );
		}
		return o;
	}
	
	public ProminentObject take(Template t, long timeout) {
		if( log.isDebugEnabled() ) {
			log.debug( "Take: " + t );
		}
		ProminentObject o = readOrWait( t, timeout );
		
		if( o == null ) {
			throw new EntryNotFoundException( "Entry not found" );
		} 
		
		// TODO notify listeners.
		
		objectSpace.remove( o );
		return o;
	}
	
	/**
	 * Returns a <code>MobileObject</code> that matches the template <code>t</code>.
	 * If no matching object is found this method will block until a matching object
	 * appears on the space and then return. If no matching entry appears 
	 * <code>null</code> will be returned.
	 * @param t
	 * @param timeout
	 * @return
	 */
	private synchronized ProminentObject readOrWait( Template t, long timeout ) {
		ProminentObject o = objectSpace.get( t );
		if( o != null || timeout == 0 ) {
			// return if entry was found or if no entry found and timeout = 0
			return o;
		}
		// calculate wait
		long now = System.currentTimeMillis();
		long waitTo = Math.max( timeout, timeout + now );
		
		if( log.isDebugEnabled() ) {
			log.debug( "Didn't find entry, waiting " + timeout + "millis" );
		}
		Thread me = Thread.currentThread();
		
		// wait for timeout amount of millis		
		while( o == null && waitTo > now ) {
				
			try {
				wait( waitTo - now );
			} catch (InterruptedException e) {
				if( log.isDebugEnabled() ) {
					log.debug( "Interrupted, perhaps entry matching template has arrived. template="+ t );
				}
			}
			/* timeout limit exceeded or notification received,
			 * try to find object again
			 */
			
			o = objectSpace.get( t );
			if( o != null && log.isDebugEnabled() ) {
				log.debug( "Waiting for entry to arrive was successful" );
			}
			// update now-reference used to calculate wait-time time
			if( o == null ) {
				now = System.currentTimeMillis();
			}
		}
		return o;
	}


	public ObjectSpace getObjectSpace() {
		return objectSpace;
	}


	public void setObjectSpace(ObjectSpace objects) {
		this.objectSpace = objects;
	}
}
